#include "wrapping_integers.hh"

#include <iostream>

using namespace std;

const uint64_t round_size{1ul << 32};

//! Transform an "absolute" 64-bit sequence number (zero-indexed) into a WrappingInt32
//! \param n The input absolute 64-bit sequence number
//! \param isn The initial sequence number
WrappingInt32 wrap(uint64_t n, WrappingInt32 isn) {
    const uint32_t offset = static_cast<uint32_t>(n % round_size);
    return isn + offset;
}

//! Transform a WrappingInt32 into an "absolute" 64-bit sequence number (zero-indexed)
//! \param n The relative sequence number
//! \param isn The initial sequence number
//! \param checkpoint A recent absolute 64-bit sequence number
//! \returns the 64-bit sequence number that wraps to `n` and is closest to `checkpoint`
uint64_t unwrap(WrappingInt32 n, WrappingInt32 isn, uint64_t checkpoint) {
    uint32_t offset = static_cast<uint32_t>(n - isn);
    // round down the checkpoint to the multiple of 2^32
    uint64_t base{(checkpoint / round_size) * round_size};
    // for better performance, "bigger" start from
    // a value close to the checkpoint rather than a very small value
    uint64_t bigger{static_cast<uint64_t>(offset) + base};
    // Does seqno n have a smaller absolute seqno?
    // smaller is 0 if not.
    uint64_t smaller{bigger > round_size ? bigger - round_size : 0};
    // Certainly: smaller < checkpoint < bigger
    while (bigger < checkpoint) {
        smaller = bigger;
        bigger += round_size;
    }
    // n relates to two possible result "smaller" and "bigger"
    // the closest to checkpoint is the real result
    // round_size < smaller < checkpoint < bigger
    if ((smaller != 0) && (checkpoint - smaller < bigger - checkpoint)) {
        return smaller;
    }
    // n relates to only one possible result "bigger"
    // 0 = smaller < checkpoint < bigger < round_size
    return bigger;
}
